package util;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.zip.GZIPInputStream;

import static com.google.common.collect.Maps.newHashMap;
import static org.apache.commons.lang3.StringUtils.isEmpty;

/**
 * Created by jeff.zhou on 2017/7/28.
 * 创建HTTP客户端工具类
 */
@SuppressWarnings("unchecked")
public class WebUtils {

    private static Boolean ignoreHostCheck;
    private static Boolean ignoreSSLCheck;

    private WebUtils() {
    }

    public static void setIgnoreSSLCheck(boolean ignoreSSLCheck) {
        WebUtils.ignoreSSLCheck = ignoreSSLCheck;
    }

    public static void setIgnoreHostCheck(boolean ignoreHostCheck) {
        WebUtils.ignoreHostCheck = ignoreHostCheck;
    }

    public static String doPost(String url, String ctype, byte[] content, int connectTimeout, int readTimeout) throws IOException {
        return _doPost(url, ctype, content, connectTimeout, readTimeout, null);
    }

    public static String doPost(String url, String ctype, byte[] content, int connectTimeout, int readTimeout, Map<String, String> headerMap) throws IOException {
        return _doPost(url, ctype, content, connectTimeout, readTimeout, headerMap);
    }

    private static String _doPost(String url, String ctype, byte[] content, int connectTimeout, int readTimeout, Map<String, String> headerMap) throws IOException {
        HttpURLConnection conn = null;
        OutputStream out = null;
        String rsp = null;

        try {
            conn = getConnection(new URL(url), "POST", ctype, headerMap);
            conn.setConnectTimeout(connectTimeout);
            conn.setReadTimeout(readTimeout);
            out = conn.getOutputStream();
            out.write(content);
            rsp = getResponseAsString(conn);
        } finally {
            if(out != null) {
                out.close();
            }

            if(conn != null) {
                conn.disconnect();
            }

        }

        return rsp;
    }


    private static byte[] getTextEntry(String fieldName, String fieldValue, String charset) throws IOException {
        String entry = "Content-Disposition:form-data;name=\"" +
                fieldName +
                "\"\r\nContent-Type:text/plain\r\n\r\n" +
                fieldValue;
        return entry.getBytes(charset);
    }

    private static byte[] getFileEntry(String fieldName, String fileName, String mimeType, String charset) throws IOException {
        String entry = "Content-Disposition:form-data;name=\"" +
                fieldName +
                "\";filename=\"" +
                fileName +
                "\"\r\nContent-Type:" +
                mimeType +
                "\r\n\r\n";
        return entry.getBytes(charset);
    }

    public static String doGet(String url, Map<String, String> params) throws IOException {
        return doGet(url, params, "UTF-8");
    }

    private static String doGet(String url, Map<String, String> params, String charset) throws IOException {
        HttpURLConnection conn = null;
        String rsp = null;

        try {
            String ctype = "application/x-www-form-urlencoded;charset=" + charset;
            String query = buildQuery(params, charset);
            conn = getConnection(buildGetUrl(url, query), "GET", ctype, null);
            rsp = getResponseAsString(conn);
        } finally {
            if(conn != null) {
                conn.disconnect();
            }

        }

        return rsp;
    }

    private static HttpURLConnection getConnection(URL url, String method, String ctype, Map<String, String> headerMap) throws IOException {
        Object conn = url.openConnection();
        if(conn instanceof HttpsURLConnection) {
            HttpsURLConnection i$ = (HttpsURLConnection)conn;
            if(ignoreSSLCheck) {
                try {
                    SSLContext entry = SSLContext.getInstance("TLS");
                    entry.init(null, new TrustManager[]{new TrustAllTrustManager()}, new SecureRandom());
                    i$.setSSLSocketFactory(entry.getSocketFactory());
                    i$.setHostnameVerifier((hostname, session) -> true);
                } catch (Exception e) {
                    throw new IOException(e);
                }
            } else if(ignoreHostCheck) {
                i$.setHostnameVerifier((hostname, session) -> true);
            }

            conn = i$;
        }

        ((HttpURLConnection)conn).setRequestMethod(method);
        ((HttpURLConnection)conn).setDoInput(true);
        ((HttpURLConnection)conn).setDoOutput(true);
        ((HttpURLConnection)conn).setRequestProperty("Host", url.getHost());
        ((HttpURLConnection)conn).setRequestProperty("Accept", "application/json");
        ((HttpURLConnection)conn).setRequestProperty("Content-Type", ctype);
        if(headerMap != null) {

            for (Object o : headerMap.entrySet()) {
                Map.Entry entry1 = (Map.Entry) o;
                ((HttpURLConnection) conn).setRequestProperty((String) entry1.getKey(), (String) entry1.getValue());
            }
        }

        return (HttpURLConnection)conn;
    }

    private static URL buildGetUrl(String url, String query) throws IOException {
        return isEmpty(query)?new URL(url):new URL(buildRequestUrl(url, query));
    }

    private static String buildRequestUrl(String url, String... queries) {
        if(queries != null && queries.length != 0) {
            StringBuilder newUrl = new StringBuilder(url);
            boolean hasQuery = url.contains("?");
            boolean hasPrepend = url.endsWith("?") || url.endsWith("&");
            for (String query : queries) {
                if (!isEmpty(query)) {
                    if (!hasPrepend) {
                        if (hasQuery) {
                            newUrl.append("&");
                        } else {
                            newUrl.append("?");
                            hasQuery = true;
                        }
                    }

                    newUrl.append(query);
                    hasPrepend = false;
                }
            }

            return newUrl.toString();
        } else {
            return url;
        }
    }

    private static String buildQuery(Map<String, String> params, String charset) throws IOException {
        if(params != null && !params.isEmpty()) {
            StringBuilder query = new StringBuilder();
            Set entries = params.entrySet();
            boolean hasParam = false;

            for (Object entry1 : entries) {
                Map.Entry entry = (Map.Entry) entry1;
                String name = (String) entry.getKey();
                String value = (String) entry.getValue();
                if (areNotEmpty(name, value)) {
                    if (hasParam) {
                        query.append("&");
                    } else {
                        hasParam = true;
                    }

                    query.append(name).append("=").append(URLEncoder.encode(value, charset));
                }
            }

            return query.toString();
        } else {
            return null;
        }
    }

    private static String getResponseAsString(HttpURLConnection conn) throws IOException {
        String charset = getResponseCharset(conn.getContentType());
        if(conn.getResponseCode() < 400) {
            String contentEncoding = conn.getContentEncoding();
            return "gzip".equalsIgnoreCase(contentEncoding)?getStreamAsString(new GZIPInputStream(conn.getInputStream()), charset):getStreamAsString(conn.getInputStream(), charset);
        } else {
            throw new IOException(conn.getResponseCode() + " " + conn.getResponseMessage());
        }
    }

    private static String getStreamAsString(InputStream stream, String charset) throws IOException {
        try {
            InputStreamReader reader = new InputStreamReader(stream, charset);
            StringBuilder response = new StringBuilder();
            char[] buff = new char[1024];

            int read1;
            while((read1 = reader.read(buff)) > 0) {
                response.append(buff, 0, read1);
            }

            return response.toString();
        } finally {
            if(stream != null) {
                stream.close();
            }

        }
    }

    private static String getResponseCharset(String ctype) {
        String charset = "UTF-8";
        if(!isEmpty(ctype)) {
            String[] params = ctype.split(";");

            for (String param1 : params) {
                String param = param1;
                param = param.trim();
                if (param.startsWith("charset")) {
                    String[] pair = param.split("=", 2);
                    if (pair.length == 2 && !isEmpty(pair[1])) {
                        charset = pair[1].trim();
                    }
                    break;
                }
            }
        }

        return charset;
    }

    public static String decode(String value) {
        return decode(value, "UTF-8");
    }

    public static String encode(String value) {
        return encode(value, "UTF-8");
    }

    private static String decode(String value, String charset) {
        String result = null;
        if(!isEmpty(value)) {
            try {
                result = URLDecoder.decode(value, charset);
            } catch (IOException var4) {
                throw new RuntimeException(var4);
            }
        }

        return result;
    }

    private static String encode(String value, String charset) {
        String result = null;
        if(!isEmpty(value)) {
            try {
                result = URLEncoder.encode(value, charset);
            } catch (IOException var4) {
                throw new RuntimeException(var4);
            }
        }

        return result;
    }

    public static Map<String, String> splitUrlQuery(String query) {
        HashMap<String,String> result = newHashMap();
        String[] pairs = query.split("&");
        if(pairs.length > 0) {

            for (String pair : pairs) {
                String[] param = pair.split("=", 2);
                if (param.length == 2) {
                    result.put(param[0], param[1]);
                }
            }
        }

        return result;
    }

    private static class TrustAllTrustManager implements X509TrustManager {
        TrustAllTrustManager() {
        }

        public X509Certificate[] getAcceptedIssuers() {
            return null;
        }

        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        }

        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        }
    }

    private static boolean areNotEmpty(String... values) {
        boolean result = true;
        if(values != null && values.length != 0) {

            for (String value : values) {
                result &= !isEmpty(value);
            }
        } else {
            result = false;
        }

        return result;
    }
}
